package pd.struct;

import pd.struct.statement.StatementObject;
import java.util.Iterator;

/**
 * An iterator for traversing script trees.
 *
 * @author Joshua Swank
 */
public class ScriptTreeIterator implements Iterator
{
    /**
     * The tree being traversed by the iterator. Should never be null.
     */
    private ScriptTree tree;

    /**
     * The current focus of the iterator. Can be null.
     */
    private ScriptNode currentNode = null;

    /**
     * The traversal method used by the iterator. Should never be null.
     */
    private Traversal traversal = Traversal.getDefault();

    /**
     * Initializes a new iterator for traversing the specified tree. This should only be called by the ScriptTree.createIterator(..) functions.
     *
     * @param tree the tree traversed by the new iterator.
     */
    protected ScriptTreeIterator( ScriptTree tree )
    {
        this.tree = tree;
    }

    /**
     * Initializes a new iterator for traversing the specified tree using the specified traversal method.
     *
     * @param tree the tree traversed by the new iterator.
     * @param traversal the traversal method used by the new iterator.
     */
    protected ScriptTreeIterator( ScriptTree tree, Traversal traversal )
    {
        this.tree = tree;
        this.traversal = traversal;
    }

    /**
     * Sets the iterator's method of traversal.
     *
     * @param traversal the iterator's new traversal method.
     */
    public void setTraversal( Traversal traversal )
    {
        if( traversal != null )
            this.traversal = traversal;
    }

    /**
     * Returns true if the tree has a node in front of it using the current traversal method.
     *
     * @return true if the tree has a node in front of it using the current traversal method, false otherwise.
     */
    public boolean hasNext()
    {
        return hasNext( traversal );
    }

    /**
     * Returns true if the tree has a node in front of it using the specified traversal method.
     *
     * @param traversal the traversal method to use.
     * @return true if the tree has a node in front of it using the specified traversal method, false otherwise.
     */
    public boolean hasNext( Traversal traversal )
    {
        if( getPosition( traversal ) >= tree.size() )
            return false;
        else
            return true;
    }

    /**
     * Advances the iterator to the next in the series using the current traversal method.
     *
     * @return the next node's value or null if it doesn't exist.
     */
    public StatementObject next()
    {
        return next( traversal );
    }

    /**
     * Advances the iterator to the next in the series using the specified traversal method.
     *
     * @param traversal the traversal method to use.
     * @return the next node's value or null if it doesn't exist.
     */
    public StatementObject next( Traversal traversal )
    {
        ScriptNode node = nextNode( traversal );

        if( node != null )
            return node.getValue();
        else
            return null;
    }

    /**
     * Returns the position of the current node using the current traversal method.
     *
     * @return the position of the current node using the current traversal method.
     */
    public int getPosition()
    {
        return getPosition( traversal );
    }

    /**
     * Returns the position of the current node using the specified traversal method.
     *
     * @param traversal the traversal method to use for finding the position.
     * @return the position of the current node or 0 if there is no current node.
     */
    public int getPosition( Traversal method )
    {
        if( currentNode != null )
            return currentNode.getPosition( method );

        return 0;
    }

    /**
     * Removes the current node from the tree and makes the parent the new current node or null if there is no parent.
     */
    public void remove()
    {
        if( currentNode != null )
        {
            currentNode = currentNode.remove();

            if( currentNode == null )
                tree.setRoot( null );
        }
    }

    /**
     * Adds a new child to the current node.
     *
     * @param child the value of the new child.
     */
    public void addChild( StatementObject child )
    {
        currentNode.addChild( child );
    }

    /**
     * Returns the statement object associated with the current node.
     *
     * @return the value of the current node or null if it doesn't exist.
     */
    public StatementObject get()
    {
        if( currentNode != null )
            return currentNode.getValue();
        else
            return null;
    }

    /**
     * Returns the subtree of the current node.
     *
     * @return the current node's subtree.
     */
    public ScriptTree getSubtree()
    {   
        return new ScriptTree( currentNode.cloneSubtree() );
    }

    /**
     * Advances the iterator to the parent of the current node.
     *
     * @return the value of the parent node or null if it doesn't exist.
     */
    public StatementObject toParent()
    {
        if( currentNode != null )
        {
            currentNode = currentNode.getParent();

            return currentNode.getValue();
        }
        else
            return null;
    }

    /**
     * Advances the iterator to the specified child or stays where it is if the child doesn't exist.
     *
     * @param child the index of the child to advance to.
     * @return the value of the child or null if it doesn't exist.
     */
    public StatementObject toChild( int child )
    {
        ScriptNode c = null;

        if( currentNode != null )
            c = currentNode.getChild( child );

        if( c != null )
        {
            currentNode = c;
            return currentNode.getValue();
        } else
            return null;
    }

    /**
     * Returns the depth of the current node.
     *
     * @return the depth of the current node or 0 if it doesn't exist.
     */
    public int getDepth()
    {
        if( currentNode != null )
            return currentNode.getDepth();
        else
            return 0;
    }

    /**
     * Returns an array containing the positions of the node's previous n ancestors using the current traversal method.
     *
     * @param n the number of ancestors to retrieve.
     * @return an array of length min( depth-1, n ) containing the positions of the node's ancestors or null if n < 0.
     */
    public int[] getAncestorPositions( int n )
    {
        return getAncestorPositions( n, traversal );
    }

    /**
     * Returns an array containing the positions of the node's previous n ancestors using the specified traversal method.
     *
     * @param n the number of ancestors to retrieve.
     * @param method the method used to determine each ancestor's position.
     * @return an array of length min( depth-1, n ) containing the positions of the node's ancestors or null if n < 0.
     */
    public int[] getAncestorPositions( int n, Traversal method )
    {
        if( currentNode != null )
            return currentNode.getAncestorPositions( n, method );
        else
            return null;
    }

    /**
     * Returns an array containing the values of the node's previous n ancestors.
     *
     * @param n the number of ancestors to retrieve.
     * @return an array of length min( depth-1, n ) containing the values of the node's ancestors or null if n < 0.
     */
    public StatementObject[] getAncestorValues( int n )
    {
        if( currentNode != null )
            return currentNode.getAncestorValues( n );
        else
            return null;
    }

    /**
     * Moves the iterator to the specified position using the current traversal method and returns its value.
     *
     * @param index the iterator's new position.
     * @return the specified node's value or null if it doesn't exist.
     */
    public StatementObject to( int index )
    {
        return to( index, traversal );
    }

    /**
     * Moves the iterator to the specified position using the specified traversal method and returns its value.
     *
     * @param index the iterator's new position.
     * @param method the traversal method used to determine the position.
     * @return the specified node's value or null if it doesn't exist.
     */
    public StatementObject to( int index, Traversal method )
    {
        int position = getPosition();

        if( position > index )
        {
            currentNode = null;
            position = 0;
        }

        switch( method )
        {
            case PREORDER:
                for( int i = position; i < index; i++ )
                    nextPreorder();
            break;

            case POSTORDER:
                for( int i = position; i < index; i++ )
                    nextPostorder();
            break;
        }

        if( get() != null )
            return get();
        else
            return null;
    }

    /**
     * Advances the iterator to the next in the series using the current traversal method.
     *
     * @return the new current node or null if it doesn't exist.
     */
    protected ScriptNode nextNode()
    {
        return nextNode( traversal );
    }

    /**
     * Advances the iterator to the next in the series using the specified traversal method.
     *
     * @param traversal the traversal method used to find the next node.
     * @return the new current node or null if it doesn't exist.
     */
    protected ScriptNode nextNode( Traversal traversal )
    {
        if( hasNext( traversal ))
        {
            switch( traversal )
            {
                case PREORDER:
                    nextPreorder();
                break;

                case POSTORDER:
                    nextPostorder();
                break;
            }

            return currentNode;
        } else
            return null;
    }

    /**
     * Advances the iterator to the next node in a preorder traversal.
     */
    private void nextPreorder()
    {
        if( currentNode != null )
            currentNode = currentNode.nextPreorder();
        else
            currentNode = tree.getRoot();
    }

    /**
     * Advances the iterator to the next node in a postorder traversal.
     */
    private void nextPostorder()
    {
        if( currentNode != null )
            currentNode = currentNode.nextPostorder();
        else {
            currentNode = tree.getRoot().toLeft();
        }
    }
}
